/*
 *  Copyright 2003 by Texas Instruments Incorporated.
 *  All rights reserved. Property of Texas Instruments Incorporated.
 *  Restricted rights to use, duplicate or disclose this code are
 *  granted through contract.
 *  
 */

#include <std.h>
#include <swi.h>
#include <iom.h>    
#include <atm.h>    
 
#include <csl.h>   
#include <csl_edma.h>                       
#include <csl_cache.h>
#include <csl_dat.h>
#include <csl_irq.h>


#include <evmdm642.h>
#include <evmdm642osd.h>

#define FIFO_WADDR     (EVMDM642_SYNC_BASE + 0x40)
#define CLUT_WADDR     (EVMDM642_SYNC_BASE + 0x44)
#define OSD_XSTART_ADDR           (EVMDM642_SYNC_BASE + 0x8)
#define OSD_YSTART_ADDR           (EVMDM642_SYNC_BASE + 0xC)
#define OSD_XSTOP_ADDR            (EVMDM642_SYNC_BASE + 0x10)
#define OSD_YSTOP_ADDR            (EVMDM642_SYNC_BASE + 0x14) 
#define OSD_EVTS_PER_FIELD_ADDR       (EVMDM642_SYNC_BASE + 0x18)


/* mini-driver API functions */   
static Int mdBindDev(Ptr *devp, Int devId, Ptr devParams){return IOM_COMPLETED;}
static Int mdControlChan(Ptr chanp, Uns cmd, Ptr args);
static Int mdCreateChan(Ptr *chanp, Ptr devp, String name, Int mode,
        Ptr chanParams, IOM_TiomCallback cbFxn, Ptr cbArg);
static Int mdDeleteChan(Ptr chanp);
static Int mdSubmitChan(Ptr chanp, IOM_Packet *packet);

/* local functions */
static Int configOSD(Ptr args);
static void disableMux();
static void enableMux();
static void loadCLUT(Ptr args);
static void osdEdmaISR(int tcc);
static void resetOSD(); 
static void swiFillOSDBuf();


/* global and static variables */
typedef struct OSDObj {
    Uint8* buf;
    Int    bufSz;
    Int    segId;   
    Bool   interlaced;
    EDMA_Handle hEdma;
    EDMA_Handle hRld[2];          
    Int    tcc;       
    Int    curFrmCt;
    Int    totalFrmCt;                                 
    Uint16 xStart;
    Uint16 hSize;
    Uint16 yStart;
    Uint16 vSize;   
    
    QUE_Elem dspQue;
    SWI_Handle hSWI;
    IOM_Packet *iomPacket; 
    IOM_TiomCallback    cbFxn;    
    Ptr cbArg;
}OSDObj;

static OSDObj osdObj = {
    NULL, 0, -1, TRUE, EDMA_HINV, EDMA_HINV, EDMA_HINV, -1
};

IOM_Fxns EVMDM642OSD_Fxns = {
    mdBindDev,
    (IOM_TmdUnBindDev)IOM_mdNotImpl,
    mdControlChan,
    mdCreateChan,
    mdDeleteChan,
    mdSubmitChan
};

/*
 *  ======== mdControlChan ========
 */
static Int mdControlChan(Ptr chanp, Uns cmd, Ptr args)
{
    Int retVal = IOM_COMPLETED;      

    switch(cmd) {
        case  EVMDM642OSD_CMD_RESET: 
        /* be extra when reset the OSD FPGA because this can  */
        /* also affect the working of other devices connected */
        /* to the FPGA                                        */
        resetOSD();
        break;
        case  EVMDM642OSD_CMD_SET_CLUT:
        loadCLUT(args);
        break;
        case  EVMDM642OSD_CMD_CONFIG: 
        retVal = configOSD(args);
        break;
        case  EVMDM642OSD_CMD_START:  
        enableMux();
        break;
        case  EVMDM642OSD_CMD_STOP:   
        disableMux();
        break;              
    }         
    return retVal;
}              
/*
 *  =================== mdCreateChan ============================
 *  create a channel
 */
static Int  mdCreateChan(Ptr *chanp, Ptr devp, String name, Int mode,
                      Ptr chanParams, IOM_TiomCallback cbFxn, Ptr cbArg)
{
    Int retVal = IOM_COMPLETED;     
    SWI_Attrs attrs =  SWI_ATTRS; 
    if(mode != IOM_OUTPUT){
        return IOM_EBADARGS;
    }
    *chanp = (Ptr)1;         
    osdObj.curFrmCt = 0;
    attrs.fxn = swiFillOSDBuf;
    osdObj.hSWI = SWI_create(&attrs);
    osdObj.totalFrmCt = 0;
    osdObj.iomPacket = NULL;  
    osdObj.cbFxn = cbFxn;         
    osdObj.cbArg = cbArg;
    QUE_new(&osdObj.dspQue);
    /* allocate edma channels and tcc */
    if((osdObj.hEdma = EDMA_open(EDMA_CHA_EXTINT6,EDMA_OPEN_RESET)) == EDMA_HINV
        || (osdObj.hRld[0] = EDMA_allocTable(-1)) == EDMA_HINV 
        || (osdObj.hRld[1] =EDMA_allocTable(-1)) == EDMA_HINV 
        || (osdObj.tcc = EDMA_intAlloc(EDMA_CHA_EXTINT6)) == -1){
       
        retVal = IOM_EALLOC;

    }    
    if(chanParams != NULL) {
        retVal = mdControlChan((Ptr)1, EVMDM642OSD_CMD_CONFIG, chanParams);            
    }
    if(retVal != IOM_COMPLETED) {
        mdDeleteChan(*chanp);   
    }
    return retVal;
}               


/*
 *  =================== mdDeleteChan ============================
 *  delete the channel
 */

static Int  mdDeleteChan(Ptr chanp)
{
    /* disable MUX first */
    disableMux();
    
    /* free all resources */
    if(osdObj.hEdma != EDMA_HINV) {
        EDMA_close(osdObj.hEdma);
        osdObj.hEdma = EDMA_HINV;
    }
    if(osdObj.hRld[0] != EDMA_HINV) {
        EDMA_freeTable(osdObj.hRld[0]);
        osdObj.hRld[0] = EDMA_HINV;
    }
    if(osdObj.hRld[1] != EDMA_HINV) {
        EDMA_freeTable(osdObj.hRld[1]);
        osdObj.hRld[1] = EDMA_HINV;
    }
    if(osdObj.tcc != -1) {
        EDMA_intFree(osdObj.tcc);
        osdObj.tcc = -1;
    }
    if(osdObj.buf != NULL) {
       osdObj.buf = NULL;
       MEM_free(osdObj.segId, osdObj.buf, osdObj.bufSz);
    }
    return IOM_COMPLETED;
}


/*
 *  ======== mdSubmitChan ========
 */
static Int  mdSubmitChan(Ptr chanp, IOM_Packet *packet)
{
    EVMDM642OSD_Packet *osdPacket;
    Int retVal;
    Uint32 gie = IRQ_globalDisable();
    
    if(packet->cmd == IOM_WRITE){
        osdPacket = (void *)packet->addr; /* pointer of a video I/O packet */
        QUE_put(&osdObj.dspQue, (QUE_Handle)osdPacket);        
        retVal = packet->status = IOM_PENDING;
        osdObj.iomPacket = packet;
    } else {
        retVal = IOM_EBADARGS;
    }   
    IRQ_globalRestore(gie);
    return retVal;
}


static Int configOSD(Ptr args)
{
    EVMDM642OSD_Params *params = (EVMDM642OSD_Params *)args;
    EDMA_Config  cfgEdma;
    Uint8 regOSDCtl;
    Int numEvents;

    /* make sure the OSD in the appropriate mode   */
    /* the osd driver does not configure whether   */
    /* the FPGA works in 8 or 16 bit mode, as this */
    /* is dertermined by the application in a      */
    /*higher level                                 */
    regOSDCtl = EVMDM642_rget(EVMDM642_OSDCTRL);
    if(regOSDCtl ^ (params->mode << 3)) {
        /* if mode parameter is not the same as the */
        /* current mode settings, return error      */
        return IOM_EBADMODE;
    }
    *(volatile Uint16 *)(OSD_XSTART_ADDR) = params->xStart;
    *(volatile Uint16 *)(OSD_XSTOP_ADDR)  = params->hSize + params->xStart -1;
    *(volatile Uint16 *)(OSD_YSTART_ADDR) = params->yStart;
    *(volatile Uint16 *)(OSD_YSTOP_ADDR)  = params->vSize + params->yStart -1;
    /* check to see if buffer is already freed   */
    /* this is for re-configuration of OSD after */
    /* it is initialized                         */
    if(osdObj.buf != NULL) {
        MEM_free(osdObj.segId, osdObj.buf, osdObj.bufSz);
    }
    /* first figure out the memory requirement */
    osdObj.bufSz = params->hSize * params->vSize;
    
    /* if interlaced disply, parameters is per each field */
    /* so total size must be doubled */
    osdObj.bufSz <<= params->interlaced;
        
    if( (osdObj.buf = MEM_calloc(params->segId,
        osdObj.bufSz, params->alignment))
        == NULL) {
        return IOM_EALLOC;
    }    
    CACHE_clean(CACHE_L2ALL, 0, 0);        
    osdObj.segId = params->segId;
    /* now configure the EDMA */
    cfgEdma.opt = EDMA_OPT_RMK(
        params->edmaPri,
        EDMA_OPT_ESIZE_32BIT,
        EDMA_OPT_2DS_YES, 
        EDMA_OPT_SUM_INC,
        EDMA_OPT_2DD_NO,
        EDMA_OPT_DUM_NONE,
        EDMA_OPT_TCINT_YES,
        EDMA_OPT_TCC_OF(osdObj.tcc & 0x0f), 
        EDMA_OPT_TCCM_OF(osdObj.tcc >> 4),
        EDMA_OPT_ATCINT_NO,
        EDMA_OPT_ATCC_DEFAULT,
        EDMA_OPT_PDTS_DISABLE,
        EDMA_OPT_PDTD_DISABLE,
        EDMA_OPT_LINK_YES,
        EDMA_OPT_FS_NO
    );


    cfgEdma.dst = EDMA_DST_RMK((volatile int *)FIFO_WADDR);
    numEvents = osdObj.bufSz / (params->thrld << 2);
    *(volatile Uint16 *)(OSD_EVTS_PER_FIELD_ADDR) = (numEvents >> params->interlaced);

    cfgEdma.cnt = EDMA_CNT_RMK(numEvents  - 1, params->thrld);
    cfgEdma.idx = EDMA_IDX_RMK(params->thrld << 2, 0);
    cfgEdma.src = EDMA_SRC_RMK(osdObj.buf);
    cfgEdma.rld = EDMA_RLD_RMK(0, osdObj.hRld[0]);
    EDMA_config(osdObj.hEdma, &cfgEdma);
    EDMA_config(osdObj.hRld[0], &cfgEdma);

    /* enable EDMA transfer */
    EDMA_clearChannel(osdObj.hEdma);
    EDMA_enableChannel(osdObj.hEdma);

    /* map the edma interrupt */
    /* The EDMA interrupt dispatcher will be called by the */
    /* BIOS HWI interrupt dispatcher.                      */
    IRQ_map(IRQ_EVT_EDMAINT, params->irqId);
    HWI_dispatchPlug(params->irqId, (Fxn)EDMA_intDispatcher, -1, NULL);

    /* enable edma interrupt triggired by the completion osd data transfer */
    EDMA_intClear(osdObj.tcc);
    EDMA_intEnable(osdObj.tcc);
    /* hook the osd isr to the EDMA interrupt dispatcher */
    EDMA_intHook(osdObj.tcc, osdEdmaISR);     

    /* now load the the OSD clut */
    loadCLUT(params->cLUT);
    
    /* program the FIFO threshold */
    EVMDM642_rset(EVMDM642_THRESHLSB, (params->thrld & 0xff));
    EVMDM642_rset(EVMDM642_THRESHMSB, (params->thrld >> 8));
    
    osdObj.xStart = params->xStart;
    osdObj.hSize = params->hSize;
    osdObj.yStart = params->yStart;
    osdObj.vSize = params->vSize;
    osdObj.interlaced = params->interlaced;
    
    return IOM_COMPLETED;
}

static void disableMux()
{
    Uint8 regOSDCtl;
    
    regOSDCtl = EVMDM642_rget(EVMDM642_OSDCTRL);
    regOSDCtl &= ~2;
    EVMDM642_rset(EVMDM642_OSDCTRL, regOSDCtl);
}

static void enableMux()
{
    Uint8 regOSDCtl;
    
    regOSDCtl = EVMDM642_rget(EVMDM642_OSDCTRL);
    regOSDCtl |= 2;
    EVMDM642_rset(EVMDM642_OSDCTRL, regOSDCtl);
}


static void loadCLUT(Ptr args)
{
    Uint8 regOSDCtl;
    EVMDM642OSD_CLUT* clut = (EVMDM642OSD_CLUT *)args;  
    Int i;

    /* clear the CLUT first */
    regOSDCtl = EVMDM642_rget(EVMDM642_OSDCTRL) | 0x10;
    EVMDM642_rset(EVMDM642_OSDCTRL, regOSDCtl);

    /* now enable writing to it */
    regOSDCtl &= (~0x10);
    EVMDM642_rset(EVMDM642_OSDCTRL, regOSDCtl);
    
    for( i = 0; i < EVMDM642OSD_CLUT_SIZE; i ++) {
        *(volatile int *)CLUT_WADDR = clut->table[i];
    }
}

Uint32 zero[2] = {0, 0};

static void clearBuf()
{
    int i = 0;
    int totalSize = 60000;
    /* nothing for displaying next, just zero out the buffer */
    CACHE_clean(CACHE_L2, (Uint32 *)&zero, 8);
    while( totalSize < osdObj.bufSz){
        DAT_fill(osdObj.buf+60000*i, 60000 , (Uint32 *)&zero);
        totalSize  += 60000;
        i ++;
    } 
    DAT_fill(osdObj.buf+60000*i, osdObj.bufSz + 60000 - totalSize , (Uint32 *)&zero);
    osdObj.totalFrmCt = 0;
}
volatile Int lock = FALSE;

static void osdEdmaISR(int tcc)
{
    osdObj.curFrmCt ++;
    if(osdObj.totalFrmCt == 0 || osdObj.curFrmCt >= osdObj.totalFrmCt) {
        if(!QUE_empty(&osdObj.dspQue)){               
            if(!lock) {
                SWI_post(osdObj.hSWI);
                lock = TRUE;
            }
        }else if(osdObj.totalFrmCt != 0 ){
            clearBuf();
        }
    }
}

static void swiFillOSDBuf()
{
    EVMDM642OSD_Packet *nextPacket = 
     (EVMDM642OSD_Packet *)QUE_get(&osdObj.dspQue);
    Int offset;                      
    if(nextPacket->clearFrame) {
        clearBuf();
    }
    osdObj.totalFrmCt = nextPacket->numActiveFrms;
    osdObj.curFrmCt = 0;
    if(osdObj.interlaced) {    
        Int i;
        Int offset1 = (nextPacket->osdVOffset >> 1)* osdObj.hSize 
          + nextPacket->osdHOffset;
        Int offset2 = offset1 + osdObj.vSize  * osdObj.hSize;
        for( i = 0; i < nextPacket->osdVSize >> 1; i ++) {
            DAT_copy(nextPacket->buf + 2 * i * nextPacket->osdHSize,
              osdObj.buf + offset1 + osdObj.hSize  * i, nextPacket->osdHSize);
            DAT_copy(nextPacket->buf + (2 * i + 1) * nextPacket->osdHSize,
              osdObj.buf + offset2 + osdObj.hSize * i,
              nextPacket->osdHSize);
        }

    } else {
        offset = nextPacket->osdVOffset * osdObj.hSize  
          + nextPacket->osdHOffset;
        DAT_copy2d(DAT_1D2D, nextPacket->buf, 
          osdObj.buf + offset, nextPacket->osdHSize, 
          nextPacket->osdVSize, osdObj.hSize );
    }
    /* now call the call-back functions of the IOM packet */
    osdObj.iomPacket->status = IOM_COMPLETED;
    osdObj.cbFxn(osdObj.cbArg, osdObj.iomPacket);
    osdObj.iomPacket = NULL;                     
    ATM_seti(&lock, FALSE);
}



static void resetOSD()
{
    EVMDM642_rset(EVMDM642_OSDCTRL, 1); /* reset */
    EVMDM642_rset(EVMDM642_OSDCTRL, 0); /* out of reset */
}
